/**
 * 自动化预约 - 稳健工具模块
 *
 * @fileoverview 为自动化预约系统提供稳健性支持的核心工具库。
 * 包含重试机制、页面检测、延迟控制、日志输出、截图保存等关键功能。
 *
 * 核心功能：
 * - 智能重试机制（指数退避、条件重试）
 * - 页面状态检测和等待
 * - 彩色日志输出和格式化
 * - 页面截图和快照保存
 * - 延迟控制和防抖
 * - 错误聚合和分析
 * - 性能监控和统计
 *
 * @author System
 * @version 1.0.0
 * @since 2024
 *
 * @example
 * ```typescript
 * import { retryOperation, waitForPageReady, takeSnapshot, log } from './robust-helpers'
 *
 * // 重试操作
 * await retryOperation(async () => {
 *   await page.click('#submit-button')
 * }, 3, 1000)
 *
 * // 等待页面就绪
 * await waitForPageReady(page)
 *
 * // 保存页面截图
 * await takeSnapshot(page, 'login-success', './snapshots')
 * ```
 */

import type { Page } from 'playwright'
import fs from 'node:fs/promises'
import path from 'node:path'
import process from 'node:process'
import * as cheerio from 'cheerio'
import dayjs from 'dayjs'
import { getConfig, getLogsPath } from '../../config/index'
import {
  createContextLogger,
  generateTraceId,
  PerformanceLogger,
  log as systemLog,
} from '../../logger/index'
import 'dayjs/locale/zh-cn'

// 配置 dayjs 中文
dayjs.locale('zh-cn')

const logger = createContextLogger({ component: 'RobustHelpers' })

// ============================================================================
// 颜色和日志工具
// ============================================================================

/**
 * 终端颜色常量
 *
 * @description ANSI颜色代码，用于在终端中输出彩色文本
 * @constant colors
 */
export const colors = {
  /** 重置颜色 */
  reset: '\x1B[0m',
  /** 绿色（成功） */
  green: '\x1B[32m',
  /** 红色（错误） */
  red: '\x1B[31m',
  /** 黄色（警告） */
  yellow: '\x1B[33m',
  /** 蓝色（信息） */
  blue: '\x1B[34m',
  /** 青色 */
  cyan: '\x1B[36m',
  /** 品红色 */
  magenta: '\x1B[35m',
  /** 粗体 */
  bold: '\x1B[1m',
  /** 灰色 */
  gray: '\x1B[37m',
}

/**
 * 彩色日志输出函数
 *
 * @description 在终端输出带颜色的日志，同时记录到系统日志
 * @param {string} message - 日志消息
 * @param {string} [color] - 颜色代码，默认为reset
 * @param {any} [context] - 附加上下文信息
 *
 * @example
 * ```typescript
 * log('操作成功', colors.green)
 * log('处理用户数据', colors.blue, { userId: 123 })
 * ```
 */
export function log(message: string, color = colors.reset, context?: any): void {
  const timestamp = dayjs().format('HH:mm:ss')
  console.log(`${color}[${timestamp}] ${message}${colors.reset}`)

  // 同时记录到新日志系统 - 移除emoji表情符号
  const cleanMessage = message.replace(/[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/gu, '').trim()
  logger.debug(cleanMessage, context)

  // 追加到 JSONL 文件，确保终端日志也可离线查看
  try {
    const filePath = path.join(getLogsPath(), 'automation-ui.jsonl')
    const record = { t: dayjs().valueOf(), level: 'info', message: cleanMessage }
    void fs.appendFile(filePath, `${JSON.stringify(record)}\n`, 'utf8').catch(() => {})
  }
  catch {}
}

export function logError(message: string, context?: any): void {
  log(`❌ ${message}`, colors.red, context)
  const cleanMessage = message.replace(/[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/gu, '').trim()
  logger.error(cleanMessage, context)
}

/**
 * 信息日志输出
 *
 * @description 输出蓝色的信息级别日志
 * @param {string} message - 日志消息
 * @param {any} [context] - 附加上下文
 */
export function logInfo(message: string, context?: any): void {
  log(`ℹ️ ${message}`, colors.blue, context)
  const cleanMessage = message.replace(/[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/gu, '').trim()
  logger.info(cleanMessage, context)
}

/**
 * 成功日志输出
 *
 * @description 输出绿色的成功级别日志
 * @param {string} message - 日志消息
 * @param {any} [context] - 附加上下文
 */
export function logSuccess(message: string, context?: any): void {
  log(`✅ ${message}`, colors.green, context)
  const cleanMessage = message.replace(/[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/gu, '').trim()
  logger.info(cleanMessage, context)
}

/**
 * 警告日志输出
 *
 * @description 输出黄色的警告级别日志
 * @param {string} message - 日志消息
 * @param {any} [context] - 附加上下文
 */
export function logWarn(message: string, context?: any): void {
  log(`⚠️ ${message}`, colors.yellow, context)
  const cleanMessage = message.replace(/[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/gu, '').trim()
  logger.warn(cleanMessage, context)
}

// ============================================================================
// 时间控制和重试机制
// ============================================================================

/**
 * 异步延迟函数
 *
 * @description 暂停执行指定的毫秒数，常用于控制操作间隔
 * @param {number} ms - 延迟时间（毫秒）
 * @returns {Promise<void>}
 *
 * @example
 * ```typescript
 * await delay(1000) // 暂停1秒
 * await delay(500)  // 暂停0.5秒
 * ```
 */
export async function delay(ms: number): Promise<void> {
  await new Promise(resolve => setTimeout(resolve, ms))
}

// 全局可选的取消信号（由执行器注入），用于让重试等待可被中断
let __abortSignal: AbortSignal | null = null

export function setAbortSignalForRobust(signal: AbortSignal | null): void {
  __abortSignal = signal || null
}

async function cancellableDelay(ms: number): Promise<void> {
  const signal = __abortSignal
  if (!signal)
    return await delay(ms)
  let timer: any
  return await new Promise<void>((resolve, reject) => {
    const onAbort = () => {
      cleanup()
      reject(new Error('已取消'))
    }
    const cleanup = () => {
      try { clearTimeout(timer) }
      catch {}
      try { signal.removeEventListener('abort', onAbort as any) }
      catch {}
    }
    try { signal.addEventListener('abort', onAbort as any, { once: true } as any) }
    catch {}
    timer = setTimeout(() => {
      cleanup()
      resolve()
    }, ms)
  })
}

/**
 * 重试操作选项配置
 *
 * @interface RetryOptions
 * @description 配置重试操作的行为参数
 */
export interface RetryOptions {
  /** 最大重试次数 */
  attempts?: number
  /** 基础退避延迟（毫秒） */
  backoffMs?: number
  /** 退避倍数因子 */
  factor?: number
  /** 重试时的回调函数 */
  onRetry?: (attempt: number, error: Error) => void
}

/**
 * 通用重试操作函数
 *
 * @description 对异步操作提供自动重试机制，支持指数退避和详细的日志记录。
 * 广泛用于网络请求、页面操作等可能临时失败的操作。
 *
 * @template T - 操作返回值类型
 * @param {() => Promise<T>} operation - 要重试的异步操作
 * @param {string} description - 操作描述，用于日志输出
 * @param {number} [maxAttempts] - 最大重试次数
 * @param {number} [delayMs] - 重试间隔（毫秒）
 *
 * @returns {Promise<T>} 操作成功时的返回值
 * @throws {Error} 当所有重试都失败时抛出最后一次的错误
 *
 * @example
 * ```typescript
 * // 重试点击操作
 * await retryOperation(
 *   () => page.click('#submit-button'),
 *   '点击提交按钮',
 *   3,
 *   2000
 * )
 *
 * // 重试网络请求
 * const data = await retryOperation(
 *   () => fetch('/api/data').then(r => r.json()),
 *   '获取数据',
 *   5,
 *   1000
 * )
 * ```
 */
export async function retryOperation<T>(
  operation: () => Promise<T>,
  description: string,
  maxAttempts = 3,
  delayMs = 1000,
): Promise<T> {
  const operationId = generateTraceId()
  PerformanceLogger.start(`retry-operation-${operationId}`)

  let lastError: Error
  const context = { operationId, description, maxAttempts, delayMs }

  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      log(`🔄 ${description} (第${attempt}/${maxAttempts}次)`, colors.cyan, {
        ...context,
        attempt,
        operationType: 'retry-attempt',
      })

      PerformanceLogger.start(`attempt-${operationId}-${attempt}`)
      const result = await operation()
      const attemptDuration = PerformanceLogger.end(`attempt-${operationId}-${attempt}`)
      const totalDuration = PerformanceLogger.end(`retry-operation-${operationId}`)

      log(`✅ ${description} 成功`, colors.green, {
        ...context,
        attempt,
        attemptDuration,
        totalDuration,
        operationType: 'retry-success',
      })

      return result
    }
    catch (error) {
      lastError = error as Error
      const attemptDuration = PerformanceLogger.measure(`attempt-${operationId}-${attempt}`) || 0

      log(`❌ ${description} 失败: ${lastError.message}`, colors.red, {
        ...context,
        attempt,
        attemptDuration,
        error: lastError.message,
        operationType: 'retry-failure',
      })

      if (attempt < maxAttempts) {
        const waitTime = delayMs * 1.5 ** (attempt - 1)
        log(`⏳ 等待 ${waitTime}ms 后重试...`, colors.yellow, {
          ...context,
          attempt,
          waitTime,
          operationType: 'retry-wait',
        })
        await cancellableDelay(waitTime)
      }
    }
  }

  const totalDuration = PerformanceLogger.measure(`retry-operation-${operationId}`) || 0
  const finalError = new Error(`${description} 在 ${maxAttempts} 次尝试后失败: ${lastError!.message}`)

  systemLog.system.error(finalError, 'RobustHelpers.retryOperation', {
    operationId,
    description,
    maxAttempts,
    totalDuration,
    user_action: `执行重试操作: ${description}`,
    recovery_suggestion: '检查网络连接和目标服务状态',
  })

  throw finalError
}

// 等待页面就绪
export async function waitForPageReady(page: Page, description: string, timeout = 30000): Promise<boolean> {
  try {
    log(`⏳ 等待页面就绪: ${description}`, colors.blue)

    // 等待页面加载状态
    await page.waitForLoadState('networkidle', { timeout })
    await page.waitForLoadState('domcontentloaded', { timeout })

    // 额外等待确保页面稳定
    await delay(1000)

    log(`✅ 页面就绪: ${description}`, colors.green)
    return true
  }
  catch {
    log(`⚠️ 页面就绪等待超时: ${description}`, colors.yellow)
    return false
  }
}

// 检查元素是否存在
export async function checkElementExists(page: Page, selector: string, description: string): Promise<boolean> {
  try {
    const element = await page.locator(selector).first()
    const isVisible = await element.isVisible()

    if (isVisible) {
      log(`✅ 元素存在: ${description}`, colors.green)
      return true
    }
    else {
      log(`❌ 元素不可见: ${description}`, colors.yellow)
      return false
    }
  }
  catch {
    log(`❌ 元素不存在: ${description}`, colors.red)
    return false
  }
}

// 检查JavaScript函数是否存在
export async function checkFunctionExists(page: Page, functionName: string): Promise<boolean> {
  try {
    const exists = await page.evaluate((fn) => {
      return typeof window[fn as keyof Window] === 'function'
    }, functionName)

    if (exists) {
      log(`✅ 函数存在: ${functionName}`, colors.green)
      return true
    }
    else {
      log(`❌ 函数不存在: ${functionName}`, colors.red)
      return false
    }
  }
  catch {
    log(`❌ 函数检查失败: ${functionName}`, colors.red)
    return false
  }
}

// 保存页面快照
export async function takeSnapshot(page: Page, name: string, debugDir = 'automation-snapshots'): Promise<void> {
  // 由配置分别控制 HTML 与截图
  let saveHtml = false
  let saveShot = false
  try {
    const cfg = getConfig() as any
    saveHtml = !!(cfg?.logging?.save_html)
    saveShot = !!(cfg?.logging?.save_snapshots)
  }
  catch {}
  const operationId = generateTraceId()
  PerformanceLogger.start(`snapshot-${operationId}`)

  const doOnce = async () => {
    const timestamp = dayjs().format('YYYY-MM-DD_HH-mm-ss')
    const snapshotDir = path.resolve(process.cwd(), debugDir)

    // 确保目录存在
    await fs.mkdir(snapshotDir, { recursive: true })

    // 轻微稳定等待，降低导航中的上下文销毁概率
    try {
      await page.waitForLoadState('domcontentloaded', { timeout: 3000 })
    }
    catch {}
    await delay(120)

    // 获取页面信息（逐项容错）
    const pageInfo: any = { url: '', title: '', timestamp: dayjs().toISOString(), viewport: null, userAgent: '' }
    try {
      pageInfo.url = page.url()
    }
    catch {}
    try {
      pageInfo.title = await page.title()
    }
    catch {}
    try {
      pageInfo.viewport = page.viewportSize?.() || null
    }
    catch {}
    try {
      pageInfo.userAgent = await page.evaluate(() => navigator.userAgent)
    }
    catch {}

    // 保存HTML（容错，受配置开关控制）
    let htmlContent = ''
    let htmlPath = ''
    if (saveHtml) {
      try {
        htmlContent = await page.content()
      }
      catch {}
      htmlPath = path.join(snapshotDir, `${timestamp}_${name}.html`)
      try {
        await fs.writeFile(htmlPath, htmlContent, 'utf8')
      }
      catch {}
    }

    // 保存截图（容错与退让）
    let screenshotPath = ''
    if (saveShot) {
      screenshotPath = path.join(snapshotDir, `${timestamp}_${name}.png`)
      try {
        await page.screenshot({ path: screenshotPath, fullPage: true })
      }
      catch {
        try {
          await delay(300)
          await page.screenshot({ path: screenshotPath, fullPage: false })
        }
        catch {}
      }
    }

    // 保存页面信息
    const infoPath = path.join(snapshotDir, `${timestamp}_${name}.json`)
    try {
      await fs.writeFile(infoPath, JSON.stringify(pageInfo, null, 2), 'utf8')
    }
    catch {}

    const duration = PerformanceLogger.end(`snapshot-${operationId}`)

    const context = {
      operationId,
      name,
      url: pageInfo.url,
      title: pageInfo.title,
      htmlSize: saveHtml ? `${(htmlContent.length / 1024).toFixed(2)}KB` : 'disabled',
      duration,
      htmlPath,
      screenshotPath,
      infoPath,
    }

    log(`📸 已保存快照: ${name}`, colors.cyan, context)
    logger.info('快照保存成功', context)
  }

  try {
    await doOnce()
  }
  catch (error) {
    // 针对导航/上下文销毁，短暂等待后重试一次
    const msg = String((error as any)?.message || error)
    if (/Execution context was destroyed|Target page, context or browser has been closed/i.test(msg)) {
      try {
        await delay(500)
        await doOnce()
        return
      }
      catch {}
    }

    const duration = PerformanceLogger.measure(`snapshot-${operationId}`) || 0
    const context = {
      operationId,
      name,
      debugDir,
      duration,
      error: String(error),
    }

    log(`⚠️ 保存快照失败: ${name} - ${error}`, colors.yellow, context)

    if (error instanceof Error) {
      systemLog.system.error(error, 'RobustHelpers.takeSnapshot', {
        ...context,
        user_action: '系统尝试保存页面快照',
        recovery_suggestion: '检查磁盘空间和文件权限',
      })
    }
  }
}

// 识别页面类型
export function identifyPageType(html: string): {
  isHome: boolean
  isTerms: boolean
  isService: boolean
  isPersonalInfo: boolean
  isCalendar: boolean
  isTimeslot: boolean
  isConfirm: boolean
  isResult: boolean
  isError: boolean
} {
  const $ = cheerio.load(html)
  const bodyText = $('body').text()
  const url = $('base').attr('href') || ''

  return {
    isHome: url.includes('cmdShowHome') || bodyText.includes('開始預約'),
    isTerms: url.includes('cmdStartNewApplication') || bodyText.includes('條款及細則'),
    isService: bodyText.includes('選擇服務類型') || bodyText.includes('DI'),
    isPersonalInfo: bodyText.includes('個人資料') || bodyText.includes('護照號碼'),
    isCalendar: bodyText.includes('選擇預約日期') || bodyText.includes('日曆'),
    isTimeslot: url.includes('cmdGetTimeslotListAction') || bodyText.includes('選擇時間'),
    isConfirm: url.includes('cmdConfirmAppointment') || bodyText.includes('確認預約'),
    isResult: url.includes('cmdShowResult') || bodyText.includes('預約結果'),
    isError: bodyText.includes('錯誤') || bodyText.includes('失敗'),
  }
}

// 提取表单字段
export function extractFormFields(html: string): string[] {
  const $ = cheerio.load(html)
  const fields: string[] = []

  $('input, select, textarea').each((_, element) => {
    const name = $(element).attr('name')
    if (name) {
      fields.push(name)
    }
  })

  return fields
}

// 提取日历信息
export function extractCalendarInfo(html: string): {
  availableDates: string[]
  currentMonth: string
  hasNextMonth: boolean
} {
  const $ = cheerio.load(html)
  const availableDates: string[] = []

  // 查找可用日期链接
  $('a[onclick*="onClickDate"]').each((_, element) => {
    const onclick = $(element).attr('onclick')
    if (onclick) {
      const match = onclick.match(/onClickDate\('([^']+)'\)/)
      if (match) {
        availableDates.push(match[1])
      }
    }
  })

  const currentMonth = $('select[name="month"] option[selected]').text() || ''
  const hasNextMonth = $('input[onclick*="onClickNextMonth"]').length > 0

  return {
    availableDates,
    currentMonth,
    hasNextMonth,
  }
}

// 解析可用日期
export function parseAvailableDates(html: string): string[] {
  const $ = cheerio.load(html)
  const dates: string[] = []

  $('a[onclick*="onClickDate"]').each((_, element) => {
    const onclick = $(element).attr('onclick') || ''
    const match = onclick.match(/onClickDate\('([^']+)'\)/)
    if (match) {
      dates.push(match[1])
    }
  })

  return dates
}

// 解析可用时段
export function parseAvailableTimeslots(html: string): Array<{
  id: string
  date: string
  startTime: string
  endTime: string
  displayTime: string
}> {
  const $ = cheerio.load(html)
  const timeslots: Array<{
    id: string
    date: string
    startTime: string
    endTime: string
    displayTime: string
  }> = []

  $('a.redlink[onclick*="onClickTimeslot"]').each((_, element) => {
    const onclick = $(element).attr('onclick') || ''
    const text = $(element).text().trim()
    const match = onclick.match(/onClickTimeslot\('([^']+)',\s*'([^']+)',\s*'([^']+)',\s*'([^']+)'\)/)

    if (match) {
      timeslots.push({
        id: match[1],
        date: match[2],
        startTime: match[3],
        endTime: match[4],
        displayTime: text,
      })
    }
  })

  return timeslots
}

// 解析预约结果
export function parseBookingResult(html: string): {
  success: boolean
  referenceNumber?: string
  error?: string
  message: string
} {
  const $ = cheerio.load(html)
  const bodyText = $('body').text()

  // 检查成功
  if (bodyText.includes('預約成功') || bodyText.includes('预约成功')) {
    const refMatch = bodyText.match(/參考編號[：:]\s*([A-Z0-9-]+)/i)
    return {
      success: true,
      referenceNumber: refMatch?.[1],
      message: '预约成功',
    }
  }

  // 检查错误
  const errorPatterns = [
    /已約滿|已约满/,
    /時段已滿|时段已满/,
    /不可預約|不可预约/,
    /預約失敗|预约失败/,
    /系統錯誤|系统错误/,
    /\[512-E-\d+\]/,
    /\[512-W-\d+\]/,
  ]

  for (const pattern of errorPatterns) {
    const match = bodyText.match(pattern)
    if (match) {
      return {
        success: false,
        error: match[0],
        message: `预约失败: ${match[0]}`,
      }
    }
  }

  return {
    success: false,
    message: '未知状态',
  }
}

// 检测错误
export function detectError(html: string): string | null {
  const $ = cheerio.load(html)
  const bodyText = $('body').text()

  const errorPatterns = [
    /錯誤|错误/,
    /失敗|失败/,
    /無效|无效/,
    /不能|不可/,
    /\[512-E-\d+\]/,
    /\[512-W-\d+\]/,
  ]

  for (const pattern of errorPatterns) {
    const match = bodyText.match(pattern)
    if (match) {
      return match[0]
    }
  }

  return null
}
